﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;
using System.Globalization;
using System.Text.RegularExpressions;
using CashResource;
using System.Windows.Forms;

namespace CashCommon
{
    public class ECash : Exception
    {
        //Constructors
        public ECash(MessageType messageType, string message, Exception ex)
            : base(message, ex)
        {
            this._msgType = messageType;
            this._threadMessage = "";
        }

        public ECash(MessageType messageType, string message, string threadMessage)
            : base(message, null)
        {
            this._msgType = messageType;
            this._threadMessage = threadMessage;
        }

        // Properties
        public MessageType MessageType { get { return this._msgType; } }

        /// <summary>
        /// Обработка исключения
        /// </summary>
        /// <param name="exception">Сгенерированное исключение</param>
        public static void HandleError(Exception exception)
        {
            try
            {
                StringBuilder stringBuilder = new StringBuilder();
                ECash e = exception as ECash;

                if (e != null) // Исключение обработанное
                {
                    switch (e.MessageType)
                    {
                        case MessageType.AllertLog:
                            ShowError(e.Message);
                            ToLog(MessageType.AllertLog, String.Format("{0}\nThread error:\n{1}", exception.Message, e._threadMessage));
                            ToLogLevelException(exception, 0, stringBuilder);
                            break;
                        case MessageType.UserMessage:
                            ShowError(e.Message);
                            break;
                        case MessageType.SilentLog:
                            ToLog(MessageType.AllertLog, String.Format("{0}\nThread error:\n{1}", exception.Message, e._threadMessage));
                            ToLogLevelException(exception, 0, stringBuilder);
                            break;
                        default:
                            return;
                    }
                }
                else
                {
                    ShowError(CommonError.UnhandledError);
                    ToLog(MessageType.AllertLog, CommonError.UnhandledError);
                    ToLogLevelException(exception, 0, new StringBuilder());
                    ToLogEmptyErrorStack();
                }
            }
            catch (Exception exc)
            {
                try
                {
                    ShowError("Fatal Non-UI Error. Could not write the error to the event log. Reason:\n" + exc.Message);
                }
                finally
                {
                    Application.Exit();
                }
            }
        }
        
        /// <summary>
        /// Запись ошибки в файл. Ошибка записывается в лог файл с именем таким же как у приложения, но с расширением .log
        /// </summary>
        /// <param name="messageType">Тип сообщения об ошибке</param>
        /// <param name="data">Текст ошибки</param>
        public static void ToLog(MessageType messageType, string data)
        {
            try
            {
                using (FileStream fileStream = new FileStream(_log, FileMode.Append, FileAccess.Write, FileShare.None))
                {
                    using (StreamWriter streamWriter = new StreamWriter(fileStream))
                    {
                        StringBuilder _strBuild = new StringBuilder("");
                        switch (messageType)
                        {
                            case MessageType.AllertLog:
                                _strBuild.Append("\n***ALLERT_LOG***\n" + DateTime.Now.ToString() + "\t");
                                break;
                            case MessageType.SilentLog:
                                _strBuild.Append("\n***SILENT_LOG***\n" + DateTime.Now.ToString() + "\t");
                                break;
                            case MessageType.UserMessage:
                            case MessageType.None:
                            default:
                                break;
                        }
                        streamWriter.WriteLine(_strBuild.ToString() + data);
                    }
                }
            }
            catch (Exception err)
            {
                ShowError(err.Message);
            }
        }
        
        /// <summary>
        /// Запись в файл лога стека ошибок
        /// </summary>
        /// <param name="e">Исключение</param>
        /// <param name="exceptionLevel">Уровень исключения (верхний - 0)</param>
        /// <param name="logString">Текст ошибки</param>
        public static void ToLogLevelException(Exception e, int exceptionLevel, StringBuilder logString)
        {
            object[] arrayErr = { exceptionLevel, e.GetType().Name.ToString(), e.HelpLink, e.Message, e.Source, e.StackTrace, e.TargetSite };
            logString.AppendLine(String.Format("***Exception Level: {0}", exceptionLevel));
            logString.AppendLine(String.Format("***ExceptionType: {0}", e.GetType().Name.ToString()));
            logString.AppendLine(String.Format("***HelpLine: {0}", e.HelpLink));
            logString.AppendLine(String.Format("***Message: {0}", e.Message));
            logString.AppendLine(String.Format("***Source: {0}", e.Source));
            logString.AppendLine(String.Format("***StackTrace: {0}", e.StackTrace));
            logString.AppendLine(String.Format("***TargetSite: {0}", e.TargetSite));
            logString.AppendLine(String.Format("***Data: {0}", e.Data));
            foreach (DictionaryEntry de in e.Data)
            {
                logString.AppendLine(String.Format(CultureInfo.InvariantCulture, "  {0} : {1}", de.Key, de.Value));
            }
            ToLog(MessageType.None, logString.ToString());

            Exception ie = e.InnerException;
            while (ie != null)
            {
                ToLogLevelException(ie, ++exceptionLevel, new StringBuilder());
                if (exceptionLevel > 1)
                    ie = ie.InnerException;
                else
                    ie = null;
            }
            exceptionLevel--;
        }

        /// <summary>
        /// Сам не помню, что такое :)
        /// </summary>
        public static void ToLogEmptyErrorStack()
        {
            string currentStackTrace = System.Environment.StackTrace;
            string firstStackTraceCall = "System.Environment.get_StackTrace( )";

            int posOfStackTraceCall = currentStackTrace.IndexOf(firstStackTraceCall, StringComparison.Ordinal);
            string finalStackTrace = currentStackTrace.Substring(posOfStackTraceCall + firstStackTraceCall.Length);
            MatchCollection methodCallMatches = Regex.Matches(finalStackTrace, @"\sat\s.*(\sin\s.*\:line\s\d*)?");
            StringBuilder log = new StringBuilder("-------------");
            foreach (Match m in methodCallMatches)
            {
                log.Append(m.Value + System.Environment.NewLine + "-------------");
            }
        }
        
        /// <summary>
        /// Вывод окна сообщения об ошибки, с поддержкой языков
        /// </summary>
        /// <param name="data">Текст ошибки</param>
        public static void ShowError(string data)
        {
            AwareMessageBox.Show(null, data, CommonError.FormErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
        }


        // Private
        protected MessageType _msgType;
        protected string _threadMessage;
        protected static string _log = Path.ChangeExtension(Application.ExecutablePath, "log");

    }

    #region Типы исключений

    /// <summary>
    /// Исключение уровня драйвера ФРК
    /// </summary>
    public class ECashDriver : ECash
    {
        public ECashDriver(MessageType msgType, string message, Exception ex)
            : base(msgType, message, ex) { }
        public ECashDriver(MessageType msgType, string message, string threadMessage)
            : base(msgType, message, threadMessage) { }

    }

    /// <summary>
    /// Исключение уровня интерфейса приложения
    /// </summary>
    public class ECashUsers : ECash 
    {
        public ECashUsers(MessageType msgType, string message, Exception ex) 
            : base(msgType, message, ex) { }
        public ECashUsers(MessageType msgType, string message, string threadMessage)
            : base(msgType, message, threadMessage) { }
    }

    /// <summary>
    /// Исключение уровня базы данных
    /// </summary>
    public class ECashSql : ECash
    {
        public ECashSql(MessageType msgType, string message, Exception ex)
            : base(msgType, message, ex) { }
        public ECashSql(MessageType msgType, string message, string threadMessage)
            : base(msgType, message, threadMessage) { }
    }


    #endregion

    /// <summary>
    /// Тип сообщения об ошибке
    /// </summary>
    public enum MessageType
    {
        None,    
        /// <summary>
        /// Сообщение записывается в log-файл, для пользователя не выводится
        /// </summary>
        SilentLog,      
        /// <summary>
        /// Сообщение записывается в log-файл, и отображается для пользователя
        /// </summary>
        AllertLog,    
        /// <summary>
        /// Сообщение отображается для пользователя и не записывается в лог-файл
        /// </summary>
        UserMessage
    }

}